﻿using System;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Carto;
using System.Collections.Generic;

namespace PlanningGIS.ArcGIS
{
    /// <summary>
    /// 概述：ESRI.ArcGIS.Geometry 扩展类<br />
    /// </summary>
    [ComVisible(true)]
    public static class GeometryExtension
    {
        #region WKT
        /// <summary>
        /// 将指定的 ESRI.ArcGIS.Client.IGeometry 的值转换为等效的 WKT 格式字符串。
        /// </summary>
        /// <param name="geometry">需要进行转换的 ESRI.ArcGIS.Client.IGeometry。</param>
        /// <returns>等效于 ESRI.ArcGIS.Client.IGeometry 的值的 WKT 格式字符串。</returns>
        /// <remarks>目前只支持多边形的转换。</remarks>
        public static string ToWkt(this IGeometry geometry)
        {
            switch (geometry.GeometryType)
            {
                case esriGeometryType.esriGeometryPolygon:
                    return (geometry as IPolygon4).ToWkt();
                case esriGeometryType.esriGeometryLine:
                    return (geometry as IPolyline).ToWkt();
                case esriGeometryType.esriGeometryPoint:
                    return (geometry as IPoint).ToWkt();
                case esriGeometryType.esriGeometryMultipoint:
                    return (geometry as IMultipoint).ToWkt();
                default:
                    return null;
            }
        }

        /// <summary>
        /// 将点对象转换为等效的 WKT 格式字符串。
        /// </summary>
        /// <param name="point">2D点对象</param>
        /// <returns></returns>
        public static string ToWkt(this IPoint point)
        {
            return string.Format("POINT({0} {1})", point.X, point.Y);
        }

        /// <summary>
        /// 将多点对象转换为等效的 WKT 格式字符串。
        /// WKT:MULTIPOINT(1.01 2.02, 2.01 3.01)注意：这个结构与OGC标准不同，这是sqlite的几何体的结构。
        /// OGC标准： MULTIPOINT( (1.01 2.02), (2.01 3.01))
        /// </summary>
        /// <param name="point">2D多点对象</param>
        /// <returns></returns>
        public static string ToWkt(this IMultipoint point)
        {
            return string.Format("MULTIPOINT({0})", (point as IPointCollection).AsText());
        }

        /// <summary>
        /// 将线对象转换为等效的 WKT 格式字符串。
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public static string ToWkt(this IPolyline line)
        {
            return string.Format("LINESTRING({0})", (line as IPointCollection).AsText());
        }

        /// <summary>
        /// 未完成
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string ToWkt(this IMultiPatch path)
        {
            return string.Format("MULTILINESTRING((668540.706869 4858267.857562,668545.871539 4858270.322537),(668535.504206 4858270.059492, 668535.504206 4858270.059492))", 1);
        }

        /// <summary>
        /// 将指定的 ESRI.ArcGIS.Client.IPolygon4 的值转换为等效的 WKT 格式字符串。
        /// </summary>
        /// <param name="polygon">需要进行转换的 ESRI.ArcGIS.Client.IPolygon4。</param>
        /// <returns>等效于 ESRI.ArcGIS.Client.IPolygon4 的值的 WKT 格式字符串。</returns>
        public static string ToWkt(this IPolygon4 polygon)
        {
            StringBuilder stringBuilder = new StringBuilder();
            if (polygon.ExteriorRingCount == 1)
            {
                stringBuilder.Append("POLYGON ");
            }
            else
            {
                stringBuilder.Append("MULTIPOLYGON (");
            }
            IGeometryCollection exteriorRingGeometryCollection = polygon.ExteriorRingBag as IGeometryCollection;    // 外环
            for (int i = 0; i < exteriorRingGeometryCollection.GeometryCount; i++)
            {
                stringBuilder.Append("(");

                IRing exteriorRing = exteriorRingGeometryCollection.get_Geometry(i) as IRing;
                stringBuilder.Append(string.Format("({0})", (exteriorRing as IPointCollection).AsText()));

                IGeometryCollection interiorRingGeometryCollection = polygon.get_InteriorRingBag(exteriorRing) as IGeometryCollection;  // 内环
                for (int j = 0; j < interiorRingGeometryCollection.GeometryCount; j++)
                {
                    IRing interiorRing = interiorRingGeometryCollection.get_Geometry(j) as IRing;
                    stringBuilder.Append(string.Format(",({0})", (interiorRing as IPointCollection).AsText()));
                }

                stringBuilder.Append("),");
            }
            stringBuilder.Remove(stringBuilder.Length - 1, 1);
            if (polygon.ExteriorRingCount > 1)
            {
                stringBuilder.Append(")");
            }
            return stringBuilder.ToString();
        }

        /// <summary>
        /// 获取空间对象文本格式
        /// </summary>
        /// <param name="pointCollection">点集合</param>
        /// <returns></returns>
        private static string AsText(this IPointCollection pointCollection)
        {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < pointCollection.PointCount; i++)
            {
                IPoint point = pointCollection.get_Point(i);
                stringBuilder.Append(string.Format("{0} {1},", point.X, point.Y));
            }
            return stringBuilder.Remove(stringBuilder.Length - 1, 1).ToString();
        }

        /// <summary>
        /// 将指定的 WKT 格式字符串的值转换为等效的 ESRI.ArcGIS.Client.IGeometry。
        /// </summary>
        /// <param name="wkt">需要进行转换的 WKT 格式的字符串。</param>
        /// <returns>等效于 WKT 格式字符串的值的 ESRI.ArcGIS.Client.IGeometry。</returns>
        /// <remarks>目前只支持多边形的转换。</remarks>
        public static IGeometry ToGeometry(this string wkt)
        {
            return GeometryHelper.ToGeometry(wkt);
        }

        /// <summary>
        /// 将指定的 WKT 格式字符串的值转换为等效的 ESRI.ArcGIS.Geometry.IPolygon。
        /// </summary>
        /// <param name="wkt">需要进行转换的 WKT 格式的字符串。</param>
        /// <returns>等效于 WKT 格式字符串的值的 ESRI.ArcGIS.Geometry.IPolygon。</returns>
        public static IPolygon ToPolygon(this string wkt)
        {
            return GeometryHelper.ToPolygon(wkt);
        }


        //PROJCS["UTM Zone 19, Northern Hemisphere",总体投影坐标带名字UTM Zone 19, Northern Hemisphere
        //GEOGCS["Geographic Coordinate System",地理坐标系统名   Geographic Coordinate System          
        //DATUM["NAD83",基准面NAD83
        //SPHEROID["GRS 1980",6378137,298.2572220960423]椭球体GRS 1980，半径6378137和反偏率298.2572220960423],
        //PRIMEM["Greenwich",0],中央经线Greenwich，0度标准子午线
        //UNIT["degree",0.0174532925199433]转换因子0.0174532925199433],
        //PROJECTION["Transverse_Mercator"],设置投影方式
        //PARAMETER["latitude_of_origin",0],起始经度0
        //PARAMETER["central_meridian",-69],中央经线-69
        //PARAMETER["scale_factor",0.9996], 缩放比率0.9996
        //PARAMETER["false_easting",500000],坐标整体向东偏移500000
        //PARAMETER["false_northing",0],    坐标向北偏移 0  
        //UNIT["Meter",1]]                  单位   米     

        #endregion

        #region JSON
        //JSON:"{ \"x\": -118.4,\"y\": -45.2,\"spatialReference\": {\"wkid\": 4326}}";

        //JSON:{ "points": [ [ 1.01, 2.02 ], [ 2.01, 3.01 ] ], "spatialReference": { "wkid": 4326 } }

        //JSON:{ "paths": [ [ [ 668540.706869,4858267.857562], [668545.871539,4858270.322537],
        //[668535.504206,4858270.059492]]],"spatialReference": {"wkid": 102100} }

        //JSON:{"rings": [[[668540.706869,4858267.857562],[668545.871539,4858270.322537]],
        //[[668535.504206,4858270.059492],[668535.504206,4858270.059492]]],"spatialReference": {"wkid": 102100}}

        //JSON:{"rings": [[[10172081.707086032,3426616.2980572497],[10181098.35305895,3439876.071546833],
        //[1266114.9310351424,3430468.042044999],[10188842.060776865,3423858.265171416] ,[10172081.707086032,3426616.2980572497]]],
        //"spatialReference": {"wkid": 102100}}

        //JSON:{"rings": [[[752912.250297,5028764.989051],[753066.871935,5028928.677375],[753417.249537,5028775.949135],
        //[753828.826422,5027429.54477],[752992.3308,5028072.927877],[752912.250297,5028764.989051]]],
        //"spatialReference": {"wkid": 102100}}
        #endregion

        /// <summary>
        /// 将IEnvelope转成IPolygon
        /// </summary>
        /// <param name="envelope"></param>
        /// <returns></returns>
        public static IPolygon ToPolygon(this IEnvelope envelope)
        {
            ISegmentCollection segCln = new Polygon() as ISegmentCollection;
            segCln.SetRectangle(envelope);
            return segCln as IPolygon;
        }

        /// <summary>
        /// 鼠标点击一个点，以该点为圆心产生一个圆，一般用在点击查询的情况
        /// </summary>
        /// <param name="point">点对象</param>
        /// <param name="radius">注意：屏幕上的点要转化成ActivewView下面的距离，否则容易出错。</param>
        /// <param name="spatialReference"></param>
        /// <returns></returns>
        public static IPolygon ToCircle(this IPoint point, double radius, ISpatialReference spatialReference = null)
        {
            if (point == null || point.IsEmpty) return null;
            if (spatialReference == null) spatialReference = point.SpatialReference;
            if (spatialReference != null)
            {
                IUnitConverter unitConverter = new UnitConverter();
                if (spatialReference is IGeographicCoordinateSystem)
                    radius = unitConverter.ConvertUnits(radius, esriUnits.esriMeters, esriUnits.esriDecimalDegrees);
            }
            ICircularArc circle = new CircularArc();
            IConstructCircularArc constCArc = (IConstructCircularArc)new CircularArc();
            constCArc.ConstructCircle(point, radius, false);
            circle = (ICircularArc)constCArc;
            IPolygon polygon = (IPolygon)new Polygon();
            ISegmentCollection segmentCollection = (ISegmentCollection)polygon;
            object Missing = Type.Missing;
            segmentCollection.AddSegment((ISegment)circle, ref Missing, ref Missing);
            if (point.SpatialReference != null) polygon.SpatialReference = point.SpatialReference;
            return polygon;
        }

        /// <summary>
        /// 面转线，考虑多个环
        /// </summary>
        /// <param name="polygon"></param>
        /// <returns></returns>
        public static IPolyline ToPolyline(this IPolygon polygon)
        {
            IGeometryCollection lines = new Polyline() as IGeometryCollection;
            IClone clonepoly = (IClone)polygon;
            IGeometryCollection geos = (IGeometryCollection)clonepoly.Clone();
            for (int i = 0; i < geos.GeometryCount; i++)
            {
                if (geos.get_Geometry(i).GeometryType == esriGeometryType.esriGeometryRing)
                {
                    object oo = Type.Missing;
                    ISegmentCollection segCol = new Path() as ISegmentCollection;
                    segCol.AddSegmentCollection((ISegmentCollection)geos.get_Geometry(i));
                    lines.AddGeometry((IGeometry)segCol, ref oo, ref oo);
                }
            }
            return (IPolyline)lines;
        }

        /// <summary>
        /// 坐标集合转线
        /// </summary>
        /// <param name="points">坐标集合</param>
        /// <returns></returns>
        public static IPolyline ToPolyline(this List<double[]> points)
        {
            object missing = Type.Missing;
            IPolyline polyLine = new Polyline() as IPolyline;
            IPointCollection ptcol = polyLine as IPointCollection;
            foreach (double[] xy in points)
            {
                ptcol.AddPoint(new Point() { X = xy[0], Y = xy[1] }, ref missing, ref missing);
            }
            return polyLine;
        }
        /// <summary>
        /// 合并空间对象
        /// </summary>
        /// <param name="geometry"></param>
        /// <param name="geometry2"></param>
        /// <returns></returns>
        public static IGeometry Union(this IGeometry geometry, IGeometry geometry2)
        {
            return GeometryHelper.Union(geometry, geometry2);
        }

        /// <summary>
        /// 求两个多边形的相交面
        /// </summary>
        /// <param name="polygon">多边形1</param>
        /// <param name="polygon2">多边形2</param>
        /// <returns></returns>
        public static IPolygon Intersect(this IPolygon polygon, IPolygon polygon2)
        {
            return GeometryHelper.Intersect(polygon, polygon2);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="oriGeom"></param>
        /// <param name="otherGeo"></param>
        /// <returns></returns>
        public static IGeometry Difference(this IGeometry oriGeom, IGeometry otherGeo)
        {
            if (otherGeo == null || otherGeo.IsEmpty) return oriGeom;
            IGeometry pGeomClone = (otherGeo as IClone).Clone() as IGeometry;
            ITopologicalOperator2 topoOperator = pGeomClone as ITopologicalOperator2;
            if (!topoOperator.IsKnownSimple) topoOperator.Simplify();
            pGeomClone.Project(oriGeom.SpatialReference);
            //如果源图形整个被包含在裁切图形内部，则直接返回该图形即可
            IRelationalOperator rel = pGeomClone as IRelationalOperator;
            if (rel.Contains(oriGeom)) return null;
            ITopologicalOperator2 topoOperator2 = oriGeom as ITopologicalOperator2;
            if (!topoOperator2.IsKnownSimple) topoOperator2.Simplify();
            IGeometry geo = topoOperator2.Difference(topoOperator as IGeometry);
            topoOperator = geo as ITopologicalOperator2;
            if (!topoOperator.IsKnownSimple) topoOperator.Simplify();
            return geo;
        }

        /// <summary>
        /// 获取直线与x正轴方向的角度，并转为0-180之间
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public static double GetAngle(this ILine line)
        {
            double angle = (180 * line.Angle) / Math.PI;
            if (angle < 0) angle = angle + 360;
            if (angle > 180) angle = angle - 180;
            return angle;
        }

        /// <summary>
        /// 获取线与x正轴方向的角度，取值范围在0-360°之间
        /// </summary>
        /// <param name="polyline"></param>
        /// <returns></returns>
        public static double GetAngle(this IPolyline polyline)
        {
            ILine line = new Line();
            line.FromPoint = polyline.FromPoint;
            line.ToPoint = polyline.ToPoint;
            double angle = (180 * line.Angle) / Math.PI;
            if (angle < 0) angle = angle + 360;
            return angle;
        }

        /// <summary>
        /// 判断两个点是否为同一个点（由于两个点不可能坐标完全相等，这里用近似的方式判断两个点是否在同一位置）
        /// </summary>
        /// <param name="pnt1">点1</param>
        /// <param name="pnt2">点2</param>
        /// <returns>判断结构，相同为True；不同为False</returns>
        public static bool Equals2(this IPoint pnt1, IPoint pnt2)
        {
            if (Math.Abs(pnt1.X - pnt2.X) < 0.000000001)
            {
                if (Math.Abs(pnt1.Y - pnt2.Y) < 0.000000001) return true;
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="layer"></param>
        /// <param name="equallayer"></param>
        /// <returns></returns>
        public static bool Equals2(this ILayer layer, ILayer equallayer)
        {
           if(!layer.GetType().Equals(equallayer.GetType()))return false;
           if (layer is IFeatureLayer)
           {
               
           }
           return true;
        }

        /// <summary>
        /// 将经纬度数值转成米
        /// </summary>
        /// <param name="value">需要转换的数值</param>
        /// <param name="spatialReference">该值所对应的空间参考</param>
        /// <returns></returns>
        public static double ToMeter(this double value,ISpatialReference spatialReference)
        {
            if (spatialReference != null)
            {
                IUnitConverter unitConverter = new UnitConverter();
                if (spatialReference is IGeographicCoordinateSystem)
                    value = unitConverter.ConvertUnits(value, esriUnits.esriDecimalDegrees, esriUnits.esriMeters);
            }
            return value;
        }

        /// <summary>
        /// 作缓冲，考虑经纬度坐标和高斯坐标
        /// </summary>
        /// <param name="geometry">要缓冲的空间对象</param>
        /// <param name="buffer">缓存距离,米</param>
        /// <param name="spatialReference">空间参考：geometry.SpatialReference；(IFeatureClass as IGeoDataset).SpatialReference</param>
        /// <returns></returns>
        public static IGeometry GetBuffer(this IGeometry geometry, double buffer, ISpatialReference spatialReference = null)
        {
            if (spatialReference == null) spatialReference = geometry.SpatialReference;
            if (spatialReference != null)
            {
                IUnitConverter unitConverter = new UnitConverter();
                if (spatialReference is IGeographicCoordinateSystem)
                    buffer = unitConverter.ConvertUnits(buffer, esriUnits.esriMeters, esriUnits.esriDecimalDegrees);
            }

            ITopologicalOperator2 top2;
            if (geometry.GeometryType != esriGeometryType.esriGeometryPoint)
            {
                top2 = (ITopologicalOperator2)geometry;
                top2.IsKnownSimple_2 = false;
                top2.Simplify();
            }
            ITopologicalOperator top = geometry as ITopologicalOperator;
            IGeometry geomBuff = top.Buffer(buffer);
            top2 = geomBuff as ITopologicalOperator2;
            top2.IsKnownSimple_2 = false;
            top2.Simplify();
            return geomBuff;
        }

        /// <summary>
        /// 计算图形的长度或者面积
        /// </summary>
        /// <param name="geometry"></param>
        /// <returns></returns>
        public static double GetLengthOrArea(this IGeometry geometry)
        {
            double rst = 0;

            bool needConvert = false;
            ISpatialReference spatialReference = geometry.SpatialReference;

            if (spatialReference != null)
            {
                if (spatialReference is IGeographicCoordinateSystem) needConvert = true;
            }

            if (geometry.GeometryType == esriGeometryType.esriGeometryPolyline)
            {
                IPolyline pPolyline = geometry as IPolyline;
                rst = pPolyline.Length;

                if (needConvert)
                {
                    IUnitConverter unitConverter = new UnitConverter();
                    rst = unitConverter.ConvertUnits(rst, esriUnits.esriDecimalDegrees, esriUnits.esriMeters);
                }
            }
            else
            {
                IGeometry pGeomClone = (geometry as IClone).Clone() as IGeometry;

                IArea pArea = pGeomClone as IArea;
                if (needConvert)
                {
                    ISpatialReference sr = GeometryHelper.GetCGCS2000SpatialByX(pArea.Centroid.X);
                    pGeomClone.Project(sr);
                }
                rst = pArea.Area;

                pGeomClone = null;
            }

            return rst;
        }
    }
}